#include "types.h"
#include "defs.h"
#include "param.h"
#include "mmu.h"
#include "proc.h"
#include "fs.h"
#include "file.h"
#include "spinlock.h"

#define PIPESIZE 512

struct pipe {
    struct spinlock lock;
    char data[PIPESIZE];
    uint nread;     // number of bytes read
    uint nwrite;    // number of bytes written
    int readopen;   // read fd is still open
    int writeopen;  // write fd is still open
};

int pipealloc(struct file **f0, struct file **f1)
{
    struct pipe *p;

    p = 0;
    *f0 = *f1 = 0;

    if((*f0 = filealloc()) == 0 || (*f1 = filealloc()) == 0) {
        goto bad;
    }

    if((p = kmalloc (get_order(sizeof(*p)))) == 0) {
        goto bad;
    }

    p->readopen = 1;
    p->writeopen = 1;
    p->nwrite = 0;
    p->nread = 0;

    initlock(&p->lock, "pipe");

    (*f0)->type = FD_PIPE;
    (*f0)->readable = 1;
    (*f0)->writable = 0;
    (*f0)->pipe = p;
    (*f1)->type = FD_PIPE;
    (*f1)->readable = 0;
    (*f1)->writable = 1;
    (*f1)->pipe = p;

    return 0;

    //PAGEBREAK: 20
    bad:
    if(p) {
        kfree (p, get_order(sizeof*p));
    }

    if(*f0) {
        fileclose(*f0);
    }

    if(*f1) {
        fileclose(*f1);
    }

    return -1;
}

void pipeclose(struct pipe *p, int writable)
{
    acquire(&p->lock);

    if(writable){
        p->writeopen = 0;
        wakeup(&p->nread);

    } else {
        p->readopen = 0;
        wakeup(&p->nwrite);
    }

    if(p->readopen == 0 && p->writeopen == 0){
        release(&p->lock);
        kfree (p, get_order(sizeof(*p)));

    } else {
        release(&p->lock);
    }
}

//PAGEBREAK: 40
int pipewrite(struct pipe *p, char *addr, int n)
{
    int i;

    acquire(&p->lock);

    for(i = 0; i < n; i++){
        while(p->nwrite == p->nread + PIPESIZE){  //DOC: pipewrite-full
            if(p->readopen == 0 /*|| proc->killed*/){
                release(&p->lock);
                return -1;
            }

            wakeup(&p->nread);
            sleep(&p->nwrite, &p->lock);  //DOC: pipewrite-sleep
        }

        p->data[p->nwrite++ % PIPESIZE] = addr[i];
    }

    wakeup(&p->nread);  //DOC: pipewrite-wakeup1
    release(&p->lock);
    return n;
}

int piperead(struct pipe *p, char *addr, int n)
{
    int i;

    acquire(&p->lock);

    while(p->nread == p->nwrite && p->writeopen){  //DOC: pipe-empty
        if(proc->killed){
            release(&p->lock);
            return -1;
        }

        sleep(&p->nread, &p->lock); //DOC: piperead-sleep*/
    }

    for(i = 0; i < n; i++){  //DOC: piperead-copy
        if(p->nread == p->nwrite) {
            break;
        }

        addr[i] = p->data[p->nread++ % PIPESIZE];
    }

    wakeup(&p->nwrite);  //DOC: piperead-wakeup
    release(&p->lock);

    return i;
}
